using System;
using Microsoft.SPOT;
using Gadgeteer.Modules;
using Gadgeteer;
using Microsoft.SPOT.Hardware;
using System.Collections;

namespace dotnetwarrior.NetMF.DL40
{
  public partial class DLIOModule : DaisyLinkModule
  {
    enum PinType : byte
    {
      Output = 0,
      Input = 1,
      Interrupt = 2,
      PulseCounter = 3,
      Capture = 4,
      AnalogInput = 5,
      PWMOutput = 6
    }

    static class Commands
    {
      // Common PIN Commands
      public const byte CMD_COMMON_CONFIG = 255;

      // Counter PIN commands
      public const byte CMD_COUNTER_RESET = 1;

      // Output PIN commands
      public const byte CMD_OUTPUTPIN_SET = 1;

      // PWM Output PIN commands
      public const byte CMD_PWMOUTPUTPIN_SETPULSE = 1;
      public const byte CMD_PWMOUTPUTPIN_RESET = 2;
    }

    abstract class Pin : IPin, IDisposable
    {
      protected byte ConfigRegister;
      protected byte CommandRegister;
      protected byte ArgRegister;
      protected byte ValueRegister;

      public byte Id { get; private set; }
      public byte PinPort { get; private set; }
      public byte PinNumber { get; private set; }
      public DLIOModule Module { get; private set; }

      public Pin(DLIOModule module, byte port, byte pin)
      {
        if (port > 3) throw new ArgumentOutOfRangeException("port");
        if (pin > 11) throw new ArgumentOutOfRangeException("pin");
        if (port == 3 && pin > 5) throw new ArgumentOutOfRangeException("pin", "Port 3 only has 6 pins");

        Module = module;
        PinPort = port;
        PinNumber = pin;
        Id = (byte)((port * 12) + pin);

        ConfigRegister = (byte)(Id + BaseRegisterSize);
        CommandRegister = (byte)(ConfigRegister + PinCount);
        ArgRegister = (byte)(CommandRegister + PinCount);
        ValueRegister = (byte)(ArgRegister + PinCount);
      }

      protected void ConfigurePin(PinType type, Port.ResistorMode resistor, params byte[] arguments)
      {
        byte config = (byte)type;

        switch (resistor)
        {
          case Microsoft.SPOT.Hardware.Port.ResistorMode.PullUp: config |= (0x00 << 4); break;
          case Microsoft.SPOT.Hardware.Port.ResistorMode.PullDown: config |= (0x01 << 4); break;
          case Microsoft.SPOT.Hardware.Port.ResistorMode.Disabled: config |= (0x02 << 4); break;
        }
        Module.WriteByte(ConfigRegister, config);

        if (arguments != null && arguments.Length > 0)
        {
          byte[] command = new byte[2 + arguments.Length];
          command[0] = CommandRegister;
          command[1] = Commands.CMD_COMMON_CONFIG;
          Array.Copy(arguments, 0, command, 2, arguments.Length);
          Module.WriteParams(command);
        }
      }

      protected virtual void Dispose(bool disposing)
      {
        Module.UnregisterPin(this);
      }

      public void Dispose()
      {
        Dispose(true);
        Module = null;

        GC.SuppressFinalize(this);
      }

      ~Pin()
      {
        Dispose(false);
      }
    }

    abstract class DigitalInputPin : Pin, IDigitalPin
    {
      public DigitalInputPin(DLIOModule module, byte port, byte pin)
        : base(module, port, pin)
      {
      }

      public bool Read()
      {
        return Module.ReadByte(ValueRegister) != 0;
      }
    }

    class InputPin : DigitalInputPin
    {
      public InputPin(DLIOModule module, byte port, byte pin, Port.ResistorMode resistor) :
        base(module, port, pin)
      {
        ConfigurePin(PinType.Input, resistor);
      }
    }

    class InterruptPin : DigitalInputPin, IInterruptPin
    {
      private Port.InterruptMode _interruptMode;

      protected byte CountRegister;

      public InterruptPin(DLIOModule module, byte port, byte pin, Port.ResistorMode resistor, Port.InterruptMode interruptMode, int glitchFilterTime) :
        base(module, port, pin)
      {
        _interruptMode = interruptMode;
        CountRegister = (byte)(ValueRegister + PinCount);

        module.RegisterPin(this, true);

        ConfigurePin(PinType.Interrupt, resistor, (byte)interruptMode,
          (byte)glitchFilterTime, (byte)(glitchFilterTime >> 8), (byte)(glitchFilterTime >> 16), (byte)(glitchFilterTime >> 24));
      }

      public event PinInterruptHandler Interrupt;
      private PinInterruptHandler _onInterrupt;

      public void OnInterrupt(IInterruptPin sender, uint interruptCount)
      {
        if (_onInterrupt == null) _onInterrupt = new PinInterruptHandler(OnInterrupt);
        if (Program.CheckAndInvoke(Interrupt, _onInterrupt, sender, interruptCount))
        {
          Interrupt(sender, interruptCount);
        }
      }

      public uint GetInterruptCount()
      {
        return Module.ReadUInt32(CountRegister);
      }
    }

    class OutputPin : DigitalInputPin, IOutputPin
    {
      public OutputPin(DLIOModule module, byte port, byte pin, bool initialState) :
        base(module, port, pin)
      {
        ConfigurePin(PinType.Output, Microsoft.SPOT.Hardware.Port.ResistorMode.PullDown);
        Write(initialState);
      }

      public void Write(bool state)
      {
        Module.WriteParams(CommandRegister, Commands.CMD_OUTPUTPIN_SET, (byte)(state ? 1 : 0));
      }
    }

    class PulseCounterPin : DigitalInputPin, IPulseCounterPin
    {
      private Port.InterruptMode _interruptMode;

      protected byte CountRegister;

      public PulseCounterPin(DLIOModule module, byte port, byte pin, Port.ResistorMode resistor, Port.InterruptMode interruptMode, bool resetOnRead) :
        base(module, port, pin)
      {
        _interruptMode = interruptMode;
        CountRegister = (byte)(ValueRegister + PinCount);

        module.RegisterPin(this, false);

        ConfigurePin(PinType.PulseCounter, resistor, (byte)interruptMode, (byte)(resetOnRead ? 1 : 0));
      }

      public ulong ReadCounter()
      {
        return Module.ReadUInt64(CountRegister);
      }

      public void ResetCounter()
      {
        Module.WriteParams(CommandRegister, Commands.CMD_COUNTER_RESET);
      }
    }

    class CapturePin : DigitalInputPin, IPulseCounterPin
    {
      private Port.InterruptMode _interruptMode;

      protected byte CountRegister;

      public CapturePin(DLIOModule module, byte port, byte pin, Port.ResistorMode resistor, Port.InterruptMode interruptMode, bool resetOnRead) :
        base(module, port, pin)
      {
        _interruptMode = interruptMode;
        CountRegister = (byte)(ValueRegister + PinCount);

        byte pinSense = 0;
        switch (interruptMode)
        {
          case Port.InterruptMode.InterruptEdgeHigh: pinSense = 1; break;
          case Port.InterruptMode.InterruptEdgeLow: pinSense = 2; break;
          default:
            throw new ArgumentException("interruptMode");
        }

        module.RegisterPin(this, false);
        ConfigurePin(PinType.Capture, resistor, pinSense, (byte)(resetOnRead ? 1 : 0));
      }

      public ulong ReadCounter()
      {
        return Module.ReadUInt64(CountRegister);
      }

      public void ResetCounter()
      {
        Module.WriteParams(CommandRegister, Commands.CMD_COUNTER_RESET);
      }

      protected override void Dispose(bool disposing)
      {
        // Disable the capture timer and set pit to default state
        ConfigurePin(PinType.Capture, Port.ResistorMode.PullUp, 0);

        base.Dispose(disposing);
      }
    }

    class AnalogInputPin : Pin, IAnalogInputPin
    {
      private const uint MaxRawValue = 1023;
      public AnalogInputPin(DLIOModule module, AnalogInputPinId pin)
        : base(module, (byte)(((byte)pin) / 12), (byte)(((byte)pin) % 12))
      {
        module.RegisterPin(this, false);
        ConfigurePin(PinType.AnalogInput, Port.ResistorMode.Disabled);
      }

      /// <summary>
      /// Read raw value (0 - 1023)
      /// </summary>
      /// <returns></returns>
      public ushort ReadRaw()
      {
        return Module.ReadUInt16(ValueRegister);
      }

      /// <summary>
      /// Read value from 0 - 1
      /// </summary>
      /// <returns></returns>
      public double Read()
      {
        uint raw = ReadRaw();
        if (raw > MaxRawValue)
          raw = MaxRawValue;

        return ((double)raw) / ((double)MaxRawValue);
      }
    }

    class PWMOutputPin : Pin, IPWMOutputPin
    {
      private uint _maxPeriod;

      public PWMOutputPin(DLIOModule module, PWMOutputPinId pinId, uint maxPeriod)
        : base(module, (byte)(((byte)pinId) / 12), (byte)(((byte)pinId) % 12))
      {
        _maxPeriod = maxPeriod;
        module.RegisterPin(this, false);
        ConfigurePin(PinType.PWMOutput, Port.ResistorMode.Disabled);
      }

      public void Set(uint frequency_hz, byte dutyCycle)
      {
        if (dutyCycle < 0 || dutyCycle > 100) throw new ArgumentOutOfRangeException("dutyCycle");
                             
        uint period;
        if (frequency_hz == 0)
        {
          period = 0;
        }
        else
        {
          period = (uint)(1000000000.0 / frequency_hz);
        }

        if (period > _maxPeriod) throw new ArgumentOutOfRangeException("frequency_hz");

        uint highTime = (uint)(period * (dutyCycle / 100.0));
        SetPulse(period, highTime);
      }

      public void SetPulse(uint period_ns, uint highTime_ns)
      {
        if (period_ns > _maxPeriod) throw new ArgumentOutOfRangeException("period_ns");
        if (highTime_ns < 0 || highTime_ns > period_ns) throw new ArgumentOutOfRangeException("highTime_ns");

        Module.WriteParams(CommandRegister, Commands.CMD_PWMOUTPUTPIN_SETPULSE,
          (byte)period_ns, (byte)(period_ns >> 8), (byte)(period_ns >> 16), (byte)(period_ns >> 24),
          (byte)highTime_ns, (byte)(highTime_ns >> 8), (byte)(highTime_ns >> 16), (byte)(highTime_ns >> 24));
      }

      protected override void Dispose(bool disposing)
      {
        Module.WriteByte(CommandRegister, Commands.CMD_PWMOUTPUTPIN_RESET);
        base.Dispose(disposing);
      }
    }
  }
}
